use core::{
    ops::{Deref, DerefMut},
    slice,
};

use cmsis_rtos2_rs::os_delay;
use periph_utils::{bootloader::boot2, flash};
use utils::{
    log, parse_usize,
    ufmt::{self, derive::uDebug},
    MutCell,
};

#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[derive(Debug, Clone, Copy, uDebug)]
pub enum Error {
    Flash(flash::Error),
    Id,
    Size,
    Md5,
}

const BASE_ADDR: u32 = 0x08000000;
const RAM_ADDR: u32 = 0x20000000;

const BOOTLOADER_SIZE: usize = parse_usize(env!("BOOTLOADER"));
const APP_SIZE: usize = parse_usize(env!("APP"));

pub const BOOTLOADER_ADDR: u32 = BASE_ADDR;
pub const APP_ADDR: u32 = BOOTLOADER_ADDR + BOOTLOADER_SIZE as u32;
const OTA_ADDR: u32 = APP_ADDR + APP_SIZE as u32;

pub const IDLE_ADDR: u32 = OTA_ADDR + APP_SIZE as u32;

struct OtaCtx {
    pkg_size: usize,
    pkg_index: usize,
    pkg_id: u16,
}

static CTX: MutCell<OtaCtx> = MutCell::const_new(OtaCtx {
    pkg_size: 0,
    pkg_index: 0,
    pkg_id: 0,
});

impl OtaCtx {
    fn init(&mut self, pkg_size: usize) {
        self.pkg_id = 0;
        self.pkg_index = 0;
        self.pkg_size = pkg_size;
    }
}

struct OtaInfo_ {
    pkg_size: usize,
    pkg_size_: usize,

    ota_addr: u32,
    ota_addr_: u32,

    app_addr: u32,
    app_addr_: u32,
}

struct OtaInfo;

impl Deref for OtaInfo {
    type Target = OtaInfo_;
    fn deref(&self) -> &Self::Target {
        unsafe { &*(RAM_ADDR as *const OtaInfo_) }
    }
}

impl DerefMut for OtaInfo {
    fn deref_mut(&mut self) -> &mut Self::Target {
        unsafe { &mut *(RAM_ADDR as *mut OtaInfo_) }
    }
}

impl OtaInfo_ {
    fn init(&mut self, pkg_size: usize) {
        self.pkg_size = pkg_size;
        self.pkg_size_ = !pkg_size;

        self.ota_addr = OTA_ADDR;
        self.ota_addr_ = !OTA_ADDR;

        self.app_addr = APP_ADDR;
        self.app_addr_ = !APP_ADDR;
    }

    fn verify(&self) -> bool {
        (self.pkg_size == !self.pkg_size_)
            && (self.app_addr == !self.app_addr_)
            && (self.ota_addr == !self.ota_addr_)
            && (self.app_addr == APP_ADDR)
            && (self.ota_addr == OTA_ADDR)
            && (self.pkg_size > 0)
    }

    fn destroy(&mut self) {
        self.pkg_size = 0;
        self.pkg_size_ = 0;

        self.ota_addr = 0;
        self.ota_addr_ = 0;

        self.app_addr = 0;
        self.app_addr_ = 0;
    }
}

pub fn on_start(pkg_size: u32) -> Result<(), Error> {
    flash::erase(OTA_ADDR, pkg_size as usize).map_err(Error::Flash)?;
    unsafe {
        CTX.as_mut().init(pkg_size as usize);
    }
    Ok(())
}

pub fn on_translate((id, pkg): (u16, &[u8])) -> Result<(), Error> {
    if id != CTX.pkg_id {
        return Err(Error::Id);
    }

    flash::write(OTA_ADDR + (CTX.pkg_index as u32), pkg.as_ptr(), pkg.len())
        .map_err(Error::Flash)?;
    unsafe {
        let ctx = CTX.as_mut();
        ctx.pkg_id += 1;
        ctx.pkg_index += pkg.len();
    }
    Ok(())
}

pub fn on_complete(pkg_md5: &[u8]) -> Result<(), Error> {
    if CTX.pkg_index != CTX.pkg_size {
        return Err(Error::Size);
    }

    let md5_data =
        md5::compute(unsafe { slice::from_raw_parts(OTA_ADDR as *const u8, CTX.pkg_size) });

    if md5_data.deref() != pkg_md5 {
        return Err(Error::Md5);
    }
    OtaInfo.init(CTX.pkg_size);
    Ok(())
}

pub fn on_boot_complete(pkg_md5: &[u8]) -> Result<(), Error> {
    if CTX.pkg_index != CTX.pkg_size {
        return Err(Error::Size);
    }

    let md5_data =
        md5::compute(unsafe { slice::from_raw_parts(OTA_ADDR as *const u8, CTX.pkg_size) });

    if md5_data.deref() != pkg_md5 {
        return Err(Error::Md5);
    }

    flash::erase(BOOTLOADER_ADDR, CTX.pkg_size).map_err(Error::Flash)?;
    flash::write(BOOTLOADER_ADDR, OTA_ADDR as _, CTX.pkg_size).map_err(Error::Flash)?;
    Ok(())
}

fn copy_ota_to_app_verify() -> Result<(), Error> {
    let flag = OtaInfo.verify();
    log!("ota flag: {}", flag);

    if flag {
        let pkg_size = OtaInfo.pkg_size;
        let app_addr = OtaInfo.app_addr;
        let ota_addr = OtaInfo.ota_addr;
        log!(
            "start pragram ota:{:08x} app:0x{:08x} size:{}",
            ota_addr,
            app_addr,
            pkg_size
        );

        flash::erase(app_addr, pkg_size).map_err(Error::Flash)?;
        flash::write(app_addr, ota_addr as _, pkg_size).map_err(Error::Flash)?;
    }
    OtaInfo.destroy();
    os_delay(10);
    Ok(())
}

pub fn run_ota() {
    log!("boot run");
    let err = if let Err(e) = copy_ota_to_app_verify() {
        e
    } else {
        unsafe {
            boot2(APP_ADDR);
        }
        return;
    };
    loop {
        os_delay(500);
        log!("ota:{:?}", err);
    }
}
